/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.core.model.impl;

import java.util.List;

import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.libermundi.theorcs.core.model.Node;
import org.libermundi.theorcs.core.model.NodeConstrain;
import org.libermundi.theorcs.core.model.NodeConstrain.Mode;
import org.libermundi.theorcs.core.model.NodeConstrainVote;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;

/**
 * @author Martin Papy
 *
 */
public abstract class AbstractNodeConstrainMethodAdvice implements MethodAdvice {
	private final static Logger logger = LoggerFactory.getLogger(AbstractNodeConstrainMethodAdvice.class);
	
	/* (non-Javadoc)
	 * @see org.apache.tapestry5.plastic.MethodAdvice#advise(org.apache.tapestry5.plastic.MethodInvocation)
	 */
	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void advise(MethodInvocation invocation) {
		if(logger.isDebugEnabled()){
			logger.debug("Filtering results");
		}
		Object result = invocation.proceed().getReturnValue();

		if(result instanceof List){
			filter((List<Node>) result);
		} else {
			Node r2 = (Node)result;
			if(!keepThisNode(r2)) {
				result = null;
			} else if(r2.hasChildren()){
				filter(r2.getChildren());
			}
		}
		invocation.setReturnValue(result);
	}

	/**
	 * @param votes
	 * @param constrain
	 */
	abstract protected void vote(List<NodeConstrainVote> votes, NodeConstrain constrain);

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void filter(List<Node> nodeList) {
		for(int i = 0; i < nodeList.size(); i++){
			Node n = nodeList.get(i);
			if(!keepThisNode(n)){
				nodeList.remove(n);
				i--;
			}
			if(n.hasChildren()){
				filter(n.getChildren());
			}
		}
	}
	
	private boolean keepThisNode(Node<?> node) {
		List<NodeConstrainVote> votes = Lists.newArrayList();
		
		if(node.hasConstrains()) {
			for( NodeConstrain c : node.getConstrains()) {
				vote(votes,c);
			}
			return calculateVotes(votes,node.getConstrainMode());
		}
		return Boolean.TRUE;
	}
	
	/**
	 * @param votes
	 * @param constrainMode
	 * @return
	 */
	private static boolean calculateVotes(List<NodeConstrainVote> votes, Mode constrainMode) {
		NodeConstrainVote finalVote = NodeConstrainVote.NEUTRAL;
		for (NodeConstrainVote nodeConstrainVote : votes) {
			if(nodeConstrainVote.equals(NodeConstrainVote.NEGATIVE)){
				finalVote = NodeConstrainVote.NEGATIVE;
				if(constrainMode.equals(Mode.ALL)){
					return Boolean.FALSE;
				}
			}

			if(nodeConstrainVote.equals(NodeConstrainVote.AFFIMATIVE)) {
				finalVote = NodeConstrainVote.AFFIMATIVE;
				if(constrainMode.equals(Mode.ANY)){
					return Boolean.TRUE;
				}
			}
		}
		if(finalVote != NodeConstrainVote.NEGATIVE) {
			return Boolean.TRUE;
		}
		return Boolean.FALSE;
	}
}
